﻿/*
 * This library began with JohnDong's MCLoginLib
 * 
 * JohnDong:              http://bit.ly/RZsaov
 * MCLoginLib:            http://bit.ly/VLcU5H
 * 
 * CraftLib is a completely rewritten version of MCLoginLib
 * Written by H4x0 (H4x0_c)
 * 
 * H4x0 Blog:             http://bit.ly/PkFaUk
 * H4x0 PasteBin:         http://bit.ly/XqXodi
 * H4x0 Minecraft Forums: http://bit.ly/VsjjNd
 * 
 * This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.
 * To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/.
 */

using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading.Tasks;

namespace CraftLib
{
    #region Account Authentication

    public class Auth
    {
        #region Session Object

        /// <summary>
        /// Session Object for launching Minecraft
        /// </summary>
        /// <seealso cref="Launcher.GenerateSession"/>
        /// <example>This sample shows how to create a Session Object
        /// <code>Auth.Session Sess = Auth.GenerateSession(Username, Password);</code>
        /// </example>
        public class Session
        {
            string _GameFilesVersion = null;
            string _PlayerUsername = null;
            string _SessionID = null;
            string _UID = null;

            public string GameFilesVersion
            {
                get { return _GameFilesVersion; }
                set { _GameFilesVersion = value; }
            }

            public string PlayerUsername
            {
                get { return _PlayerUsername; }
                set { _PlayerUsername = value; }
            }

            public string SessionID
            {
                get { return _SessionID; }
                set { _SessionID = value; }
            }

            public string UID
            {
                get { return _UID; }
                set { _UID = value; }
            }
        }

        #endregion

        /// <summary>
        /// Generate a login Session object using a username, password, and optionally a client version number
        /// </summary>
        /// <seealso cref="Auth.Session"/>
        /// <exception cref="WebException">Thrown for multiple reasons, either there was a missing parameter, the login was bad, or the account was migrated.
        /// Use exception descriptions to filter these exceptions.</exception>
        /// <param name="Username">The user's username for Minecraft.net or email for Mojang account.</param>
        /// <param name="Password">The user's password for Minecraft.net or for Mojang account.</param>
        /// <param name="ClientVersion">The current launcher version is "13", sending a value lower "12" than this will cause the server to return "Old version"
        /// (optional: Defaults to 13)</param>
        /// <returns>Returns a valid Session Object</returns>
        /// <remarks>This function will also resolve "Migrated" accounts email addresses and login with valid username tied to the email address.</remarks>
        /// <example>This sample shows how to call GenerateSession()
        /// <code>Auth.Session Sess = Auth.GenerateSession(Username, Password);</code>
        /// </example>
        public static Session GenerateSession(string Username, string Password, int ClientVersion = 13)
        {
            //MigratedName = null;                   DEPRECIATED
            //bool isEmail = Username.Contains("@"); DEPRECIATED

            #region Web Request

            Task<string[]> GetResponseAsync = Task<string[]>.Factory.StartNew(() =>
            {
                WebRequest req = WebRequest.Create(string.Format("https://login.minecraft.net?user={0}&password={1}&version={2}", Username, Password, ClientVersion));
                WebResponse resp = req.GetResponse();
                StreamReader sr = new StreamReader(resp.GetResponseStream());

                if (resp == null)
                {
                    throw new WebException("Minecraft.net appears to be offline");
                }

                return new string[] { sr.ReadToEnd().Trim() };
            });

            string Response = GetResponseAsync.Result[0];

            #endregion

            #region Handle Errors

            if (Response.Trim() == "Bad response")
            {
                throw new WebException("Request is missing a parameter");
            }
            else if (Response.Trim() == "Bad login")
            {
                throw new WebException("Account not found");
            }
            else if (Response.Trim() == "Account migrated, use e-mail as username.")
            {
                throw new WebException("Account migrated");
            }

            #endregion

            string[] Credentials = Response.Split(':');

            /* DEPRECIATED
             * 
            if (isEmail)
            {
                MigratedName = Credentials[2];
            }
            */

            #region Return Session Object

            return new Session()
            {
                GameFilesVersion = Credentials[0],
                PlayerUsername = Credentials[2],
                SessionID = Credentials[3],
                UID = Credentials[4]
            };

            #endregion
        }
    }

    #endregion

    #region File Verification

    /// <summary>
    /// For checking if Java exists and all needed Minecraft libraries
    /// </summary>
    /// <remarks>Currently checks for: Java, minecraft.jar, jinput.jar, lwjgl.jar, lwjgl_util.jar</remarks>
    public class Verify
    {
        #region Java Verification

        /// <summary>
        /// Checks if Java is installed
        /// </summary>
        /// <returns>TRUE if Java is installed, FALSE if it is not.</returns>
        /// <example>This sample shows how to call JavaInstalled()
        /// <code>
        /// bool isJavaInstalled = Verify.JavaInstalled();
        /// </code>
        /// </example>
        public static bool JavaInstalled()
        {
            string JavaVersion = null;

            try
            {
                System.Diagnostics.ProcessStartInfo procStartInfo =
                    new System.Diagnostics.ProcessStartInfo("java", "-version ");

                procStartInfo.RedirectStandardOutput = true;
                procStartInfo.RedirectStandardError = true;
                procStartInfo.UseShellExecute = false;
                procStartInfo.CreateNoWindow = true;
                System.Diagnostics.Process proc = new Process();
                proc.StartInfo = procStartInfo;
                proc.Start();
                JavaVersion = proc.StandardError.ReadToEnd();

            }
            catch
            {
                JavaVersion = null;
            }

            if (!string.IsNullOrEmpty(JavaVersion))
            {
                return true;
            }
            
            return false;
        }

        #endregion

        #region Minecraft Verification

        /// <summary>
        /// Checks if Minecraft is installed and libraries are installed
        /// </summary>
        /// <param name="CustomPath">Custom Minecraft Path</param>
        /// <returns>TRUE if Minecraft is installed, FALSE if it is not.</returns>
        /// <example>This sample shows how to call MinecraftInstalled()
        /// <code>
        /// bool isMinecraftInstalled = Verify.MinecraftInstalled();
        /// </code>
        /// </example>
        public static bool MinecraftInstalled(string IPath)
        {
            bool RetVal = true;
            string[] Files = {
                "minecraft.jar",
                "jinput.jar",
                "lwjgl.jar",
                "lwjgl_util.jar"
            };

            if (!string.IsNullOrEmpty(IPath))
            {
                if (!Directory.Exists(IPath))
                {
                    return false;
                }
            }

            Parallel.ForEach(Files, x =>
            {
                if (!File.Exists(string.Format("{0}\\{1}", IPath, x)))
                {
                    RetVal = false;
                }
            });

            return RetVal;
        }

        #endregion
    }

    #endregion

    /// <summary>
    /// For launching Minecraft
    /// </summary>
    public class Launcher
    {
        #region Properties Object

        /// <summary>
        /// GameProperties Object for launching Minecraft
        /// </summary>
        /// <seealso cref="Launcher.StartGame"/>
        /// <example>This sample shows how to create a GameProperties Object
        /// <code>
        /// Launcher.GameProperties Prop = new Launcher.GameProperties()
        /// {
        ///     GameSession = CraftLib.Auth.Session,
        ///     RAM_Minimum = 512,
        ///     RAM_Maximum = 2048,
        ///     OnlineMode = Online,
        ///     DebugMode = false
        /// };
        /// </code>
        /// </example>
        public class GameProperties
        {
            CraftLib.Auth.Session _GameSession = null;
            int _RAM_Minimum = 256;
            int _RAM_Maximum = 2048;
            bool _OnlineMode = false;
            bool _DebugMode = false;
            string _CustomPath = null;

            public CraftLib.Auth.Session GameSession
            {
                get { return _GameSession; }
                set { _GameSession = value; }
            }

            public int RAM_Minimum
            {
                get { return _RAM_Minimum; }
                set { _RAM_Minimum = value; }
            }

            public int RAM_Maximum
            {
                get { return _RAM_Maximum; }
                set { _RAM_Maximum = value; }
            }

            public bool OnlineMode
            {
                get { return _OnlineMode; }
                set { _OnlineMode = value; }
            }

            public bool DebugMode
            {
                get { return _DebugMode; }
                set { _DebugMode = value; }
            }

            public string CustomPath
            {
                get { return _CustomPath; }
                set { _CustomPath = value; }
            }
        }

        #endregion

        /// <summary>
        /// Launch Minecraft using a GameProperties Object
        /// </summary>
        /// <seealso cref="Launcher.GameProperties"/>
        /// <exception cref="System.IO.FileNotFoundException">Thrown required files are missing, either Java or Minecraft and it's libraries.</exception>
        /// <param name="Prop">GameProperties Object containing information about how the game should launch.</param>
        /// <example>This sample shows how to call StartGame()
        /// <code>
        /// Launcher.StartGame(new Launcher.GameProperties()
        /// {
        ///     GameSession = CraftLib.Auth.Session,
        ///     RAM_Minimum = 512,
        ///     RAM_Maximum = 2048,
        ///     OnlineMode = Online,
        ///     DebugMode = false,
        ///     CustomPath = "C:\My\Custom\Path\"
        /// });
        /// </code>
        /// </example>
        public static void StartGame(GameProperties Prop)
        {
            string AppData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + @"\.minecraft\bin";

            if (!string.IsNullOrEmpty(Prop.CustomPath))
            {
                AppData = Prop.CustomPath;
            }

            if (!Verify.JavaInstalled() || !Verify.MinecraftInstalled(AppData))
            {
                throw new FileNotFoundException("One or more required files are missing");
            }

            string procName = "javaw";
            string procArgv = string.Format("-Xms{0}M -Xmx{1}M -Djava.library.path={2}\\natives -cp {2}\\minecraft.jar;{2}\\jinput.jar;{2}\\lwjgl.jar;{2}\\lwjgl_util.jar net.minecraft.client.Minecraft {3}", Prop.RAM_Minimum, Prop.RAM_Maximum, AppData, Prop.GameSession.PlayerUsername);

            if (Prop.DebugMode)
            {
                procName = "java";
            }

            if (Prop.OnlineMode)
            {
                procArgv = string.Format("{0} {1}", procArgv, Prop.GameSession.SessionID);
            }

            ProcessStartInfo Inf = new ProcessStartInfo()
            {
                Arguments = procArgv,
                FileName = @"Loader.cmd",
                CreateNoWindow = true,
                UseShellExecute = false
            };

            string StartIn = Directory.GetParent(Directory.GetParent(AppData).ToString()).ToString();
            string[] Launcher = { string.Format("set APPDATA={0}", StartIn), string.Format("{0} {1}", procName, procArgv) };
            File.WriteAllLines(@"Loader.cmd", Launcher);
            Process.Start(Inf);
        }
    }
}
